﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Av.Utils;

namespace WebUtils.Models
{


    public class DbField
    {
        // Searches for field name, which could be enquoted
        private Regex _rxFieldName = new Regex(@"^(\s*)(""|`?)([\w_]+)(""|`?)");

        // Checks whether NULL or NOT NULL is set
        private Regex _rxNotNull = new Regex(@"(NOT )(\s*)(NULL)");
        private Regex _rxNull = new Regex(@"\sNULL\s");

        Match _match = null;

        public string FieldName { get; set; }

        /// <summary>
        /// Specific field (Oracle, MSSQL, ...)
        /// </summary>
        public string FieldTypeNative { get; set; }

        /// <summary>
        /// Standart field type
        /// </summary>
        public DbTypeIso FieldTypeIso { get; set; }

        public int Length { get; set; }

        public int Precision { get; set; }

        public bool IsNotNull { get; set; }

        public string FullInfo
        {
            get
            {
                return string.Format("{0,-24}{1,-12}{2}{3}", FieldName, IsNotNull ? "NOT NULL" : "NULL", FieldTypeIso, LengthPrecision);
            }
        }

        public string LengthPrecision
        {
            get
            {
                if( (Length > 0) && (Precision > 0) )
                {
                    return string.Format("({0}, {1})", Length, Precision);
                }
                if(Length > 0)
                {
                    return string.Concat("(", Length, ")");
                }
                return "";
            }
        }

        /// <summary>
        /// Extracts and sets name of field. Returns original string WITHOUT field name!
        /// </summary>
        /// <param name="s"></param>
        /// <exception cref="KeyNotFoundException">Throws exception if field name is not found</exception>
        public string ExtractFieldName(string s)
        {
            _match = _rxFieldName.Match(s);
            if( _match.Success )
            {
                FieldName = _match.Groups[3].Value;
                return s.Remove(_match.Index, _match.Length);
            }

            throw new KeyNotFoundException("Field name could not be found by mask!");
        }

        #region " Field type parsers "

        /// <summary>
        /// Extracts and sets whether field is null. Returns original string WITHOUT (NOT )NULL flag
        /// </summary>
        /// <param name="s"></param>
        public string ExtractIsNotNull(string s)
        {
            IsNotNull = false;

            _match = _rxNotNull.Match(s);
            if( _match.Success )
            {
                IsNotNull = true;
                return s.Remove(_match.Index, _match.Length);
            }

            _match = _rxNull.Match(s);
            if( _match.Success )
            {
                IsNotNull = false;
                return s.Remove(_match.Index, _match.Length);
            }

            return s;
        }

        public virtual string ExctractNative(string s)
        {
            return s;
        }

        protected Regex _rxTypeDate = new Regex(@"\bDATE\b");

        // VARCHAR, followed by in-braced number (with optional spaces) - "VARCHAR ( 255 )"
        protected Regex _rxTypeVarchar = new Regex(@"(VARCHAR)(\s*)(\()(\s*)(\d+)(\s*)(\))");

        public virtual string ExtractFieldType(string f)
        {
            FieldTypeIso = DbTypeIso.__NOT_SET__;

            Log4cs.Log(Importance.Debug, "  Looking for field type: {0}", f);
            Match m;

            // VARCHAR
            m = _rxTypeVarchar.Match(f);
            if(m.Success)
            {
                FieldTypeIso = DbTypeIso.VARCHAR;
                Length = int.Parse(m.Groups[5].Value);
                return f.Remove(m.Index, m.Length);
            }

            // DATE
            m = _rxTypeDate.Match(f);
            if(m.Success)
            {
                FieldTypeIso = DbTypeIso.DATE;
                return f.Remove(m.Index, m.Length);
            }


            return f;
        }

        #endregion


        public string GetNativeType(TypeOfDatabase typeOfDatabase)
        {
            Dictionary<int, string> ar = new Dictionary<int, string>();
            switch(typeOfDatabase)
            {
                case TypeOfDatabase.Oracle:
                    break;
                case TypeOfDatabase.Postgresql:
                    ar[(int)DbTypeIso.INTEGER] = "INTEGER";
                    ar[(int)DbTypeIso.SMALLINT] = "SMALLINT";
                    ar[(int)DbTypeIso.BIGINT] = "BIGINT";
                    ar[(int)DbTypeIso.DECIMAL] = "";
                    ar[(int)DbTypeIso.REAL] = "";
                    ar[(int)DbTypeIso.DOUBLE_PRECISION] = "";
                    ar[(int)DbTypeIso.FLOAT] = "";
                    ar[(int)DbTypeIso.BINARY] = "";
                    ar[(int)DbTypeIso.BINARY_VARYING] = "";
                    ar[(int)DbTypeIso.BINARY_LARGE_OBJECT] = "";
                    ar[(int)DbTypeIso.BOOLEAN] = "";
                    ar[(int)DbTypeIso.CHARACTER] = "";
                    ar[(int)DbTypeIso.VARCHAR] = "CHARACTER VARYING";
                    ar[(int)DbTypeIso.CHARACTER_LARGE_OBJECT] = "";
                    ar[(int)DbTypeIso.NATIONAL_CHARACTER] = "";
                    ar[(int)DbTypeIso.NATIONAL_CHARACTER_VARYING] = "";
                    ar[(int)DbTypeIso.NATIONAL_CHARACTER_LARGE_OBJECT] = "";
                    ar[(int)DbTypeIso.DATE] = "TIMESTAMP WITHOUT TIME ZONE";
                    ar[(int)DbTypeIso.TIME_WITHOUT_TIMEZONE] = "";
                    ar[(int)DbTypeIso.TIMESTAMP_WITHOUT_TIMEZONE] = "";
                    ar[(int)DbTypeIso.TIME_WITH_TIMEZONE] = "";
                    ar[(int)DbTypeIso.TIMESTAMP_WITH_TIMEZONE] = "";
                    ar[(int)DbTypeIso.INTERVAL_DAY] = "";
                    ar[(int)DbTypeIso.INTERVAL_YEAR] = "";
                    ar[(int)DbTypeIso.ARRAY] = "";
                    ar[(int)DbTypeIso.MULTISET] = "";
                    ar[(int)DbTypeIso.ROW] = "";
                    ar[(int)DbTypeIso.XML] = "";

                    if(FieldTypeIso == DbTypeIso.NUMERIC)
                    {
                        if(Precision == 0)
                        {
                            ar[(int)FieldTypeIso] = Length < 9 ? "INTEGER" : "BIGINT";
                            Length = 0;
                        }
                        else
                        {
                            ar[(int)FieldTypeIso] = "NUMERIC";
                        }
                    }

                    break;
                default:
                    break;
            }

            if( (ar.ContainsKey((int)FieldTypeIso) == false) || string.IsNullOrEmpty(ar[(int)FieldTypeIso]) )
            {
                Log4cs.Log(Importance.Error, "ISO field type [{0}] is not transalted for {1}!", FieldTypeIso, typeOfDatabase);
                throw new Exception(string.Format("ISO field type [{0}] is not transalted for {1}!", FieldTypeIso, typeOfDatabase));
            }

            return ar[(int)FieldTypeIso];
        }
    }

}
